package utils;

import java.util.ArrayList;
import java.util.Comparator;

import rasterprimate.Constants;
import rasterprimate.Primate;
import rasterprimate.PrimateTopo;
import rasterprimate.Watcher;
import repast.simphony.context.Context;
import repast.simphony.space.gis.Geography;
import repast.simphony.util.ContextUtils;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;

public final strictfp class GroupUtils {
	
	//(1)
	public static boolean checkIfSafe(Primate agent, Coordinate myFutureP){ //checks to see if there is enough neighbours with safe radius

		boolean retval = false;

		//Calculate what is in my safe radius at potential new site
		Envelope nearMe = new Envelope();
		nearMe.init(myFutureP);
		nearMe.expandBy(Constants.safeRadius);///degToMeters);
		Iterable<Primate> primatesInArea = agent.getGeog().getObjectsWithin(nearMe,Primate.class);

		if(Constants.leaderBased==true){
			//check for leading primate within my safe radius
			for (Primate p : primatesInArea){
				if (p.idNumb==0)retval=true;
			}
			//check if i'm the leader
			if (agent.idNumb==0)retval=true;		
		} else { 
			//democratic
			
			//count the number of group mates within my safe radius
			int numbPrimatesNearBy=0;
			for (Primate p : primatesInArea){
				if (p.getMyGroupId()==agent.getMyGroupId()&& p.idNumb!=agent.idNumb)numbPrimatesNearBy++;
			}
			
			//if there is enough then return value is true
			if(numbPrimatesNearBy>=agent.getSafeNeighSize()){
				retval=true;
			}
		}
		
		nearMe.setToNull();
		return retval;
	}
	
	
	//(2)
	public static Coordinate myNearestNeighs(Primate agent){//calculates the center of a group of my nearest neighbours: used when primate is not considered safe
		ArrayList<Primate> closestP = new ArrayList<Primate>();
		double minDist=99999999;
		Coordinate c = null;//new Coordinate();
		Primate pClose=null;

		//System.out.println("Agent "+agent.idNumb+" starting to run myNeigh neigh; safeNeigh size "+agent.getSafeNeighSize());

		ArrayList<Primate> allNeigh = agent.getGroupMates();

		if (allNeigh.size()>0 && allNeigh.size()<agent.getSafeNeighSize()){
			//get the closest neighbours: number of neighbours to consider is = safeNeighSize
			while (closestP.size()<agent.getSafeNeighSize() & allNeigh.size()>0){
				
				System.out.println("Neighbour list = " + allNeigh.size());
				minDist = 9999999;pClose=null;
				//take subset of primate group
				for (Primate p: allNeigh){
					double dist = agent.getCoord().distance(p.getCoord());
					if (closestP.contains(p)==false && dist < minDist){
						minDist = dist;
						pClose = p;
					}
				}

				allNeigh.remove(pClose);

				//add closest neighbour to group
				if (pClose!=null)closestP.add(pClose);
			}
		} else {
			closestP = allNeigh;
		}

		//find the center of this group (closestP)
		double xCoordAvg=0;
		double yCoordAvg=0;
		double numbPrimates=0;

		if (closestP.size()>0){
			
		//calculate their average x and y coordinates
		for (Primate p: closestP){//used to be getgroupmates()
			if (p.getMyGroupId()==agent.getMyGroupId()){
				xCoordAvg = xCoordAvg + p.getCoord().x;
				yCoordAvg = yCoordAvg + p.getCoord().y;
				numbPrimates++;
			}
		}

		xCoordAvg = xCoordAvg/numbPrimates;
		yCoordAvg = yCoordAvg/numbPrimates;

		//return the center coordinate
		c = new Coordinate(xCoordAvg,yCoordAvg);
		
		} 
		
		if (c == null){
			System.out.println("I can't find any near-by neigh! - groupUtils class, myNearestNeighs");
		}

		return c;
	}
	
	//(3)
	public static ArrayList<Primate> getPrimatesNearMe(Primate agent){

		//search for any primates within my search radius
		ArrayList<Primate> myGroupies = new ArrayList<Primate>();
		Envelope neighSearch = new Envelope();
		neighSearch.init(agent.getCoord());
		neighSearch.expandBy(Constants.primateSearchRange);

		//For any primates that have my group ID remember them as my group mates
		Iterable<Primate> primateNieghExt = agent.getGeog().getObjectsWithin(neighSearch,Primate.class);
		for (Primate p: primateNieghExt){
			//System.out.println("found some indi");
			if (p.getMyGroupId()==agent.getMyGroupId() && p.idNumb!=agent.idNumb){

				myGroupies.add(p);
			}
		}

		if(myGroupies.size()<1)System.out.println("cannot find individuals");
		neighSearch.setToNull();
		return myGroupies;

	}
	
	//(4)
	public static Coordinate getGroupCenter(ArrayList<Primate> primates){
		
		//calculate group center
		double averageX=0;
		double averageY=0;
		int numberOfInd=0;
		
		for (Primate pri:primates){
			averageX = averageX + pri.getCoord().x;
			averageY = averageY + pri.getCoord().y;
			numberOfInd++;
		}
		
		averageX = averageX/numberOfInd;
		averageY = averageY/numberOfInd;
		
		Coordinate center = new Coordinate(averageX,averageY);
		
		return center;
		
	}
	
	//(5)
	//testing threshold: e.g. does group center contain at least 80% of individuals within 100 meters
	public static boolean getGroupCenterThreshold(Coordinate center, Watcher w, int groupSize){
		
		boolean retval = false;
		
		//build envelope around center: e.g. 100 meters
		Envelope nearMe = new Envelope();
		nearMe.init(center);
		nearMe.expandBy(Constants.groupArea);
		
		//get all agents within the envelop
		Context context = ContextUtils.getContext(w);
		Geography geog = (Geography)context.getProjection("geog");
		Iterable<Primate> primatesInArea = geog.getObjectsWithin(nearMe,Primate.class);
		
		//count the number of individuals found
		int count=0;
		for (Primate p: primatesInArea){
			count++;
		}
		
		//return true if there are more individuals within the defined group area than threshold level (e.g. 80%)
		if ((double)count/(double)groupSize>Constants.groupThreshold)retval=true;		
		
		return retval;

	}

	/************************************************TOPO*************************************************************/

	//(1)
	public static boolean checkIfSafe(PrimateTopo agent, Coordinate myFutureP){ //checks to see if there is enough neighbours with safe radius

			boolean retval = false;

			//Calculate what is in my safe radius at potential new site
			Envelope nearMe = new Envelope();
			nearMe.init(myFutureP);
			nearMe.expandBy(Constants.safeRadius);///degToMeters);
			Iterable<PrimateTopo> primatesInArea = agent.getGeog().getObjectsWithin(nearMe,PrimateTopo.class);

			if(Constants.leaderBased==true){
				//check for leading primate within my safe radius
				for (PrimateTopo p : primatesInArea){
					if (p.idNumb==0)retval=true;
				}
				//check if i'm the leader
				if (agent.idNumb==0)retval=true;		
			} else { 
				//democratic
				
				//count the number of group mates within my safe radius
				int numbPrimatesNearBy=0;
				for (PrimateTopo p : primatesInArea){
					if (p.getMyGroupId()==agent.getMyGroupId()&& p.idNumb!=agent.idNumb)numbPrimatesNearBy++;
				}
				
				//if there is enough then return value is true
				if(numbPrimatesNearBy>=agent.getSafeNeighSize()){
					retval=true;
				}
			}
			
			nearMe.setToNull();
			return retval;
		}
	
		//(2)
	public static Coordinate myNearestNeighs(PrimateTopo agent){//calculates the center of a group of my nearest neighbours: used when primate is not considered safe
		ArrayList<PrimateTopo> closestP = new ArrayList<PrimateTopo>();
		double minDist=9999999;
		Coordinate c = null;//new Coordinate();
		PrimateTopo pClose=null;


		ArrayList<PrimateTopo> allNeigh = agent.getGroupMates();

		if (allNeigh.size()>0 && allNeigh.size()<agent.getSafeNeighSize()){
			//get the closest neighbours: number of neighbours to consider is = safeNeighSize
			while (closestP.size()<agent.getSafeNeighSize() & allNeigh.size()>0){

				minDist = 99999999;pClose=null;
				//take subset of primate group
				for (PrimateTopo p: allNeigh){
					double dist = agent.getCoord().distance(p.getCoord());
					if (closestP.contains(p)==false && dist < minDist){
						minDist = dist;
						pClose = p;
					}
				}

				allNeigh.remove(pClose);

				//add closest neighbour to group
				if (pClose!=null)closestP.add(pClose);
			}
		} else {
			closestP = allNeigh;
		}

		//find the center of this group (closestP)
		double xCoordAvg=0;
		double yCoordAvg=0;
		double numbPrimates=0;
		
		if (closestP.size()>0){

		//calculate their average x and y coordinates
		for (PrimateTopo p: closestP){//agent.getGroupMates()
			if (p.getMyGroupId()==agent.getMyGroupId()){
				xCoordAvg = xCoordAvg + p.getCoord().x;
				yCoordAvg = yCoordAvg + p.getCoord().y;
				numbPrimates++;
			}
		}

		xCoordAvg = xCoordAvg/numbPrimates;
		yCoordAvg = yCoordAvg/numbPrimates;

		//return the center coordinate
		c = new Coordinate(xCoordAvg,yCoordAvg);
		}
		
		if (c == null){
			System.out.println("I can't find any near-by neigh! - groupUtils class, myNearestNeighs");
		}
		
		return c;
	}
	
	//(3)
	public static ArrayList<PrimateTopo> getPrimatesNearMe(PrimateTopo agent){

		//search for any primates within my search radius
		ArrayList<PrimateTopo> myGroupies = new ArrayList<PrimateTopo>();
		Envelope neighSearch = new Envelope();
		neighSearch.init(agent.getCoord());
		neighSearch.expandBy(Constants.primateSearchRange);

		//For any primates that have my group ID remember them as my group mates
		Iterable<PrimateTopo> primateNieghExt = agent.getGeog().getObjectsWithin(neighSearch,PrimateTopo.class);
		for (PrimateTopo p: primateNieghExt){
			if (p.getMyGroupId()==agent.getMyGroupId() && p.idNumb!=agent.idNumb){
				myGroupies.add(p);
			}
		}

		if(myGroupies.size()<1)System.out.println("cannot find individuals");
		neighSearch.setToNull();
		return myGroupies;

	}
	
	//(4)
	public static Coordinate getGroupCenter(ArrayList<PrimateTopo> primates,int nothing){
		
		//calculate group center
		double averageX=0;
		double averageY=0;
		int numberOfInd=0;
		
		for (PrimateTopo pri:primates){
			averageX = averageX + pri.getCoord().x;
			averageY = averageY + pri.getCoord().y;
			numberOfInd++;
		}
		
		averageX = averageX/numberOfInd;
		averageY = averageY/numberOfInd;
		
		Coordinate center = new Coordinate(averageX,averageY);
		
		return center;
		
	}
	//(5)
	//testing threshold: Does group center contain at least 50% of individuals within 100 meters
	public static boolean getGroupCenterThreshold(Coordinate center, Watcher w,int groupSize, int nothing){
		
		boolean retval = false;
		
		//build envelope around center: 100 meters
		Envelope nearMe = new Envelope();
		nearMe.init(center);
		nearMe.expandBy(Constants.groupArea);
		
		//get all agents within the envelop
		Context context = ContextUtils.getContext(w);
		Geography geog = (Geography)context.getProjection("geog");
		Iterable<PrimateTopo> primateTopoInArea = geog.getObjectsWithin(nearMe,PrimateTopo.class);

		//count the number of individuals found
		int count=0;
		for (PrimateTopo p: primateTopoInArea){
			count++;
		}
		
		//System.out.println("Count from 100 meter buffer = "+ count + "   for "+center.toString());
		
		//return true if there are more individuals within the defined group area than threshold level (e.g. 50%)
		if ((double)count/(double)groupSize>Constants.groupThreshold)retval=true;
		
		
		return retval;
		
	}	
}
